package com.gitee.taotaojs.util.number;

import com.gitee.taotaojs.util.ConstantUtil;
import com.gitee.taotaojs.util.StringUtil;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;

/**
 * 数字格式化工具类
 *
 * @author TaoTaojs
 * @author TaoTaojs
 * @date 2020/7/2 8:50 AM
 * <p>Description</p>
 * 数字格式化工具，最底层的格式操作，应用层可以使用NumberUtil上开发
 * 关联类:
 * 参考链接：
 * History:    <author>        <time>                      <version>          <desc>
 *              TaoTaojs        2020/7/2 8:50 AM            V1.0               xingdi项目迁移整理
 *                                                                             增加大数的处理预算
 */

public final class NumberFormatUtil {

    /** 中文 -> 数字对应关系 */
    private static final Map<Character, Integer> DIGIT_CHINA = new HashMap<>();
    /** 数字 -> 中文对应关系 */
    private static final Map<Integer, Character> DIGIT_NUM = new HashMap<>();
    /** 中文 -> 增量对应关系 */
    private static final Map<Character, Integer> POSITION_CHINA = new HashMap<>();
    /** 增量 -> 中文对应关系 */
    private static final Map<Integer, Character> POSITION_NUM = new HashMap<>();

    static{
        DIGIT_CHINA.put('零', 0);
        DIGIT_CHINA.put('一', 1);
        DIGIT_CHINA.put('二', 2);
        DIGIT_CHINA.put('三', 3);
        DIGIT_CHINA.put('四', 4);
        DIGIT_CHINA.put('五', 5);
        DIGIT_CHINA.put('六', 6);
        DIGIT_CHINA.put('七', 7);
        DIGIT_CHINA.put('八', 8);
        DIGIT_CHINA.put('九', 9);
        DIGIT_NUM.put(0, '零');
        DIGIT_NUM.put(1, '一');
        DIGIT_NUM.put(2, '二');
        DIGIT_NUM.put(3, '三');
        DIGIT_NUM.put(4, '四');
        DIGIT_NUM.put(5, '五');
        DIGIT_NUM.put(6, '六');
        DIGIT_NUM.put(7, '七');
        DIGIT_NUM.put(8, '八');
        DIGIT_NUM.put(9, '九');
        POSITION_CHINA.put('十', 1);
        POSITION_CHINA.put('百', 2);
        POSITION_CHINA.put('千', 3);
        POSITION_CHINA.put('万', 4);
        POSITION_CHINA.put('亿', 7);
        POSITION_CHINA.put('兆', 11);
        POSITION_CHINA.put('京', 15);
        POSITION_CHINA.put('%', -2);
        POSITION_CHINA.put('‰', -3);
        POSITION_NUM.put(1, '十');
        POSITION_NUM.put(2, '百');
        POSITION_NUM.put(3, '千');
        POSITION_NUM.put(4, '万');
        POSITION_NUM.put(7, '亿');
        POSITION_NUM.put(11, '兆');
        POSITION_NUM.put(15, '京');
        POSITION_NUM.put(-2, '%');
        POSITION_NUM.put(-3, '‰');
    }

    private NumberFormatUtil(){

    }

    /**
     * 将阿拉伯数字转为中文数字
     * 采用StringBuilder，连接所有数字
     * 一开始判断有无负号，小数点，以及百分号
     * 等到数字完全连接成功，再将数字和负号小数点百分号标识连接起来
     * @param num 数字
     * @return 中文字符串
     */
    public static String numberToChina(Integer num){
        throw new IllegalArgumentException(num.toString());
    }

    /**
     * 中文转换成数字
     * @param numStr 数字字符串
     * @return 数字
     */
    public static Integer chinaToNumber(String numStr){
        throw new IllegalArgumentException(numStr);
    }

    /**
     * 获取字符串中第一个数字字符串
     * @param str 字符串
     * @return 第一个数字
     */
    public static String getFirstNumberStringByStr(String str){
        if(StringUtil.isBlank(str)){
            return "";
        }

        StringBuilder sb = new StringBuilder();
        boolean flag = false;
        for(int i=0; i<str.length(); i++){
            char ch = str.charAt(i);
            if (isNumberChar(ch) || isNumberCharIsChina(ch)) {
                sb.append(ch);
                flag = true;
            }else if(flag){
                break;
            }
        }
        return sb.toString();
    }

    /**
     * 将数字字符串转换为整型
     * @param numStr 数字字符串
     * @return 数字
     */
    public static Integer stringToInteger(String numStr){
        if(stringToDouble(numStr) == null){
            return null;
        }else{
            Double value = stringToDouble(numStr);
            if(value == null){
                return null;
            } else {
                return value.intValue();
            }
        }
    }

    /**
     * 将数字字符串转换为双精度
     * @param numStr 数字字符串
     * @return 双精度
     */
    public static Double stringToDouble(String numStr){
        return stringToDouble(numStr, 2);
    }
    @SuppressWarnings("checkstyle:MissingJavadocMethod")
    public static Double stringToDouble(String numStr, Integer scale){
        if(!isNumber(numStr)){
            return null;
        }
        int increment = 0;
        //判断是否存在%，且%是否在最后面
        boolean flag = isPercentByStr(numStr, ConstantUtil.PERCENT_CHAR);
        boolean flagQian = isPercentByStr(numStr, ConstantUtil.PERMILLAGE_CHAR);
        if(flag) {
            increment = -2;
            numStr = numStr.replace("%", "");
        }else if(flagQian){
            increment = -3;
            numStr = numStr.replace("‰", "");
        }
        Double doubleValue = Double.parseDouble(numStr);
        doubleValue *= Math.pow(10, increment);
        return BigDecimal.valueOf(doubleValue)
                .setScale(scale, RoundingMode.HALF_UP)
                .doubleValue();
    }

    /**
     * 单字符是否为数字
     * @param ch char，字符
     * @return 是数字返回true
     */
    public static boolean isNumberChar(char ch){
        return (ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == '%';
    }

    /**
     * 单字符是否为整型数据
     * @param ch char，字符
     * @return 是数字返回true
     */
    public static boolean isIntegerChar(char ch){
        return (ch >= '0' && ch <= '9') || ch == '-';
    }



    /**
     * 是否为特殊的增量字符
     * @param ch char，字符
     * @return 是返回ture
     */
    private static boolean isNumberCharIsChina(char ch) {
        return DIGIT_CHINA.get(ch) != null || POSITION_CHINA.get(ch) != null;
    }

    /**
     * 判断字符串是否为数字（包括百分号、千分号）
     * @param str 数字字符串
     * @return 是返回ture
     */
    public static boolean isNumber(String str){
        if(str == null || str.isEmpty()) {
            return false;
        }
        return str.matches("^([-+]?\\d+\\.?\\d*)[%,‰]?([Ee]?([-+]?\\d+\\.?\\d*)?)?$");
    }

    /**
     * 判断字符串是否是百分号数字
     * @param str 数字字符串
     * @return 是返回true
     */
    public static boolean isNumberPercent(String str){
        if(str == null || str.isEmpty()) {
            return false;
        }
        return str.matches("^(-?\\d+\\.?\\d*)[Ee]?(-?\\d+)[%,‰]$");
    }

    /**
     * 判断字符串是否是整形字符串
     * @param str 数字字符串
     * @return 是返回true
     */
    public static boolean isNumberInt(String str){
        if(str == null || str.isEmpty()) {
            return false;
        }
        return str.matches("-?[0-9]+");
    }

    /**
     * 判断传入的字符串是否为最后一个字符
     * @param numStr 字符串
     * @param slip 最后一位字符
     * @return 是返回true
     */
    private static boolean isPercentByStr(String numStr, Character slip){
        if(numStr == null || numStr.isEmpty()) {
            return false;
        }
        return numStr.indexOf(slip) == numStr.length()-1;
    }

    /**
     * 获取字符串中小数点的位数
     * 使用此方法前，推荐使用isNumber先判断是否为数字字符串
     * @param value 数字字符串
     * @return 小数点位数
     */
    public static int getDecimalDigits(String value) {
        if(StringUtil.isNotBlank(value)){
            int temp = value.indexOf(ConstantUtil.POINT_CHAR);
            if(temp > 0){
                return value.length() - temp;
            }
        }
        return 0;
    }

    /**
     * 获取字符串中小数点的位数（科学计数法）
     * 使用此方法前，推荐使用isNumber先判断是否为数字字符串
     * @param value 数字字符串
     * @return 小数点位数
     */
    public static int getScientificNotation(String value) {
        if(StringUtil.isNotBlank(value)){
            //e下的位数
            Integer eIndex = 0;
            //小数点下的位数
            Integer pointIndex = 0;

            int eTemp = value.indexOf('E');
            if(eTemp >= 0){
                String eStr = value.substring(eTemp + 1);
                eIndex = NumberUtil.getFirstNumber(eStr).intValue();
            } else if (eTemp == -1){
                eTemp = value.length();
            }

            int pointTemp = value.indexOf('.');
            if(pointTemp >= 0){
                String pointStr = value.substring(pointTemp + 1, eTemp == -1 ? 0 : eTemp);
                pointIndex = pointStr.length();
            }

            //如果e后面是正数，那小数点肯定减少，所以是一个反比例函数
            return pointIndex - eIndex;
        }
        return 0;
    }

}
